package suncertify.application;

import java.io.IOException;

import suncertify.application.exceptions.InvalidServerNameException;
import suncertify.contractors.ContractorsAccess;
import suncertify.contractors.ContractorsAccessImpl;
import suncertify.contractors.exceptions.CloseContractorsException;
import suncertify.contractors.exceptions.ContractorsInitializationException;
import suncertify.gui.ClientApplicationFrame;
import suncertify.gui.ClientApplicationFrameController;
import suncertify.gui.DesktopGui;
import suncertify.gui.dialogs.ClientConnectionDialog;
import suncertify.remoting.RmiClient;

/**
 * Represents the application in {@link ApplicationMode#STANDALONE_CLIENT} or {@link ApplicationMode#NETWORK_CLIENT}
 * modes. This class can only be created with these two enumerator values which are passed in the constructor.
 * 
 * <ul>
 * <li>This class extends the abstract base class {@link Application} and overrides the
 * {@link Application#startApplication} method. One instance of this class is created at application start in the
 * {@link Application#main(String[])} main method. The client application starts when the startApplication method is
 * called.</li>
 * <li>This class implements the {@link ClientConnectionDialogInterface} which is used by the
 * {@link ClientConnectionDialog}.</li>
 * <li>For the {@link ApplicationMode#NETWORK_CLIENT} mode this class shows the {@link ClientConnectionDialog} that asks
 * the user to connect to the server, for {@link ApplicationMode#STANDALONE_CLIENT} mode, the same dialog is shown but
 * the user is asked to open the database file located on the local machine. The {@link ClientConnectionDialog} is shown
 * as a modal child dialog of the {@link ClientApplicationFrame} dialog. After the user closes the connection dialog the
 * {@link ClientApplicationFrame} is populated with local or remote data and the end user can start working with the
 * application.</li>
 *<li>This class holds the application mode which is passed in the constructor and the {@link ContractorsAccess}
 * implementation that is the data interface, all interaction with the data in the client application is done using this
 * interface.</li>
 * <li>This class creates the {@link ContractorsAccess} interface implementation, the implementation depends on the
 * application mode, for stand alone mode one instance of the {@link ContractorsAccessImpl} is created, for the network
 * mode one instance of the <code>suncertify.remoting.RemoteContractorsAccessImpl</code> class is created. The
 * contractors access interface's implementation is only created when the connection information provided by the user
 * are valid.</li>
 * 
 * </ul>
 * 
 */
public class ClientApplication extends Application implements ClientConnectionDialogInterface {

    /**
     * Enumerator for the application mode. For the client application only {@link ApplicationMode#NETWORK_CLIENT} and
     * {@link ApplicationMode#STANDALONE_CLIENT} modes are allowed.
     */
    protected ApplicationMode applicationMode;

    /**
     * Holds contractors access interface implementation. The implementation is either an instance of the
     * {@link ContractorsAccessImpl} class or an instance of the <code>RemoteContractorsAccessImpl</code> class.
     */
    protected ContractorsAccess contractorsAccess;

    /**
     * The constructor saves the input parameter in the {@link #contractorsAccess} member variable and initializes the
     * application by calling the {@link Application#initializeApplication()} method.
     * 
     * @param applicationMode
     *            The application mode which is either {@link ApplicationMode#NETWORK_CLIENT} or
     *            {@link ApplicationMode#STANDALONE_CLIENT}.
     * 
     * @throws IllegalArgumentException
     *             if the input parameter is not one of the two accepted values mentioned above.
     */
    public ClientApplication(ApplicationMode applicationMode) {

	if (applicationMode == ApplicationMode.SERVER)
	    throw new IllegalArgumentException("Client application only accepts stand alone and network modes!");

	super.initializeApplication();

	this.applicationMode = applicationMode;
    }

    /**
     * Returns the user current working directory.
     * 
     * @return User current working directory.
     */
    public String getUserCurrentDirectory() {

	return super.getUserCurrentDirectory();
    }

    /**
     * This function starts the client application as follows:
     * <ol>
     * <li>Creates one instance of the {@link ClientApplicationFrameController} and passes the reference of this class
     * to it.</li>
     * <li>Creates one instance of the {@link ClientApplicationFrame} which provides the client application GUI. At this
     * point the client application frame contains no data and all its components like buttons and menus are disabled.</li>
     * <li>Creates one instance of the {@link ClientConnectionDialog} which asks the user to connect to the server
     * application or to open the local database file. The client connection dialog is shown as a modal child window to
     * the client application frame created earlier. At this point the function is waiting for the user input, if the
     * user cancels the client connect dialog then the application is terminated, if the user connects to the server
     * application correctly or opens the database file then the client connection dialog calls either the
     * {@link #createContractorsAccess(String)} for local mode or the
     * {@link #createRemoteContractorsAccess(String, int)} for remote server mode.</li>
     * <li>After the client connection dialog closes successfully, the {@link ContractorsAccess} implementation object
     * is assigned to the {@link ClientApplicationFrameController} created earlier.</li>
     * <li>The {@link ClientApplicationFrameController#getAllContractors()} is called which shows all contractors in the
     * application frame, then the {@link ClientApplicationFrame#enableControls()} function is called which enables all
     * controls in the frame and makes the frame ready to interact with the user.</li>
     * </ol>
     * 
     * <p>
     * If the database file is corrupt (e.g. a numeric field contains a non numeric value) then the application shows an
     * dialog that informs the user, the application terminates after the users confirms the message dialog. error
     * 
     */
    public void startApplication() {

	// Create and show the client application frame.
	ClientApplicationFrameController frameController = new ClientApplicationFrameController(this);
	ClientApplicationFrame clientWindow = new ClientApplicationFrame(frameController);

	// The client frame is shown in a disabled state. No chance the user can interact with it.

	// Create and show the client connection dialog as a modal dialog relative to the client application window.
	ClientConnectionDialog clientConnectDialog = new ClientConnectionDialog(this.applicationMode, this,
		clientWindow);

	clientConnectDialog.setVisible(true);

	// check if the connection dialog was canceled, if yes then terminate the application.
	if (clientConnectDialog.isDialogCanceled()) {
	    Log.logFine("User canceled the ClientConnectDialog");
	    this.terminateApplication();
	}

	/**
	 * Connection dialog was closed with valid data, this means we have local or remote implementation of
	 * contractorsAccess.
	 */
	frameController.setContractorsAccess(this.contractorsAccess);

	try {
	    // Load all contractors in the the table view and enable the client frame.
	    frameController.getAllContractors();
	} catch (Exception ex) {

	    Log.logSevere("Failed to read all contractors using frameController.getAllContractors()!");

	    // At this point the you have a database record/field format error e.g. the rate field contains 50.00 and
	    // not $50.00. In this case inform the user and terminate the application.
	    DesktopGui.showErrorMessage(Strings.CAP_ERROR, Strings.MSG_CLIENT_ALONE_DATABASE_ERROR);
	    this.terminateApplication();
	}

	clientWindow.enableControls();
    }

    /**
     * Creates one instance of the {@link ContractorsAccessImpl} class and assigns it to the {@link #contractorsAccess}
     * member variable, if the creation fails then an error message is shown to the user.
     * 
     * <p>
     * If the contractors access implementation is created successfully then the path is saved in the applications
     * properties file, if the save fails then an informational error message is shown to the user.
     * 
     * <p>
     * Note: Failing to save the file path in the properties file does not affect the return value of the function.
     * 
     * @param filePath
     *            path to the application's database file.
     * @return true if the {@link ContractorsAccessImpl} class is created successfully, false otherwise.
     * 
     */
    public boolean createContractorsAccess(String filePath) {

	if (filePath == null || filePath.trim().isEmpty())
	    throw new IllegalArgumentException("filePath parameter is null or empty!");

	try {
	    this.contractorsAccess = new suncertify.contractors.ContractorsAccessImpl(filePath);
	    Log.logInfo("Client application in alone mode successfully opened database file: " + filePath);

	    UserConfiguration.getInstance().setProperty(UserConfiguration.PROP_DATABASE_LOCATION, filePath);
	} catch (ContractorsInitializationException ex) {

	    Log.logInfo("suncertify.contractors.ContractorsAccessImpl(filePath) failed!");
	    DesktopGui.showErrorMessage(Strings.CAP_ERROR, Strings.MSG_CONTRACTORS_INIT_ERROR);
	    return false;
	}

	try {
	    UserConfiguration.getInstance().saveToFile();
	} catch (IOException e) {

	    Log.logInfo("UserConfiguration.getInstance().saveToFile() failed!");
	    DesktopGui.showErrorMessage(Strings.CAP_ERROR, Strings.getMessage(Strings.MSG_CONFIG_FILE_SAVE_ERROR, e
		    .toString()));
	}
	return true;
    }

    /**
     * Creates one instance of the <code>RemoteContractorsAccessImpl</code> class and assigns it the
     * {@link #contractorsAccess} member variable, if the creation fails then an error message is shown to the user.
     * 
     * <p>
     * If the contractors access implementation is created successfully then the server name and port number are saved
     * in the applications properties file, if the save fails then an informational error message is shown to the user.
     * 
     * <p>
     * Note: Failing to save the input parameters to the properties file does not affect the return value of the
     * function.
     * 
     * @param serverName
     *            Name of the remote computer that runs the application in {@link ApplicationMode#SERVER} mode.
     * @param port
     *            Port number of the remote computer.
     * @return True if the if the <code>RemotrContractorsAccessImpl</code> is created successfully, false otherwise.
     * 
     */
    public boolean createRemoteContractorsAccess(String serverName, int port) {

	try {
	    this.contractorsAccess = RmiClient.getContractorsAccess(serverName, port);

	    Log.logInfo("Client application in network mode successfully connected to server: " + serverName
		    + " on port: " + Integer.toString(port));
	    UserConfiguration.getInstance().setProperty(UserConfiguration.PROP_SERVER_NAME, serverName);
	    UserConfiguration.getInstance().setProperty(UserConfiguration.PROP_SERVER_PORT, Integer.toString(port));

	} catch (Exception e) {

	    Log.logInfo("RmiClient.getContractorsAccess(serverName, port) failed!");
	    DesktopGui.showErrorMessage(Strings.CAP_ERROR, Strings.MSG_CONTRACTORS_REMOTE_INIT_ERROR);
	    return false;
	}

	try {
	    UserConfiguration.getInstance().saveToFile();
	} catch (IOException e) {
	    DesktopGui.showErrorMessage(Strings.CAP_ERROR, Strings.getMessage(Strings.MSG_CONFIG_REMOTE_SAVE_ERROR, e
		    .toString()));
	}
	return true;
    }

    /**
     * Returns the server name property from the applications property file. If the server name property does not exist
     * in the property file then an empty string is returned.
     * 
     * @return the server name property.
     * 
     */
    public String getServerName() {

	String serverName = UserConfiguration.getInstance().getProperty(UserConfiguration.PROP_SERVER_NAME);

	if (serverName == null || serverName.isEmpty()) {

	    return "";
	} else {

	    return serverName;
	}
    }

    /**
     * Returns the {@link ApplicationMode} which was passed in the constructor.
     * 
     * @return The {@link ApplicationMode} value.
     */
    public ApplicationMode getApplicationMode() {
	return this.applicationMode;
    }

    /**
     * Closes and releases the database file.
     * 
     * @throws CloseContractorsException
     *             If the close fails because of IO errors.
     * @throws Exception
     *             If the close fails because of other errors.
     * 
     */
    public void closeDatabaseFile() throws CloseContractorsException, Exception {
	this.contractorsAccess.closeContractors();
    }

    /**
     * Validates the server computer name. Valid names are simply non empty strings.
     * 
     * @param serverName
     *            Name of the server computer.
     * @return True if the name is valid, false otherwise.
     * 
     * @throws InvalidServerNameException
     *             if the server name is invalid.
     * 
     */
    public boolean isServerNameValid(String serverName) throws InvalidServerNameException {

	if (serverName == null)
	    throw new InvalidServerNameException();

	if (serverName.trim().isEmpty()) {
	    throw new InvalidServerNameException();
	}

	/*
	 * Add any other server name validation in the future here if required, on some platforms (e.g. Microsoft
	 * Windows) computer names cannot have some forbidden characters like '[' or ']'
	 * 
	 * This version of the application considers all computer names that are not an empty string as valid computer
	 * names.
	 */

	return true;
    }
}
